export const uuid = () => {
  let timeStr = fillLength(new Date().getTime().toString(16), 12, '0');
  let s = [];
  let hexDigits = "0123456789abcdef";
  for (let i = 0; i < 20 - timeStr.length; i++) {
    s[i] = hexDigits.charAt(Math.floor(Math.random() * 0x10));
  }
  return timeStr + s.join('');
};

export const uuids = () => {
  let timeStr = fillLength(new Date().getTime().toString(16), 12, '0');
  let s = [];
  let hexDigits = "0123456789abcdef";
  for (let i = 0; i < 20 - timeStr.length; i++) {
    s[i] = hexDigits.charAt(Math.floor(Math.random() * 0x10));
  }
  return 's_' + timeStr + s.join('');
};

export const uuid36 = () => {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
    let r = Math.random() * 16 | 0;
    let v = c === 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
};

export const fillLength = (str, len, fillStr) => {
  if (typeof str === 'undefined' || str == null) {
    str = '';
  }
  if (typeof fillStr === 'undefined' || fillStr == null || fillStr === '') {
    fillStr = '0';
  }

  let fillLen = ((len || 0) - str.length)/fillStr.length;
  if (fillLen > 0) {
    for (let i=0; i<fillLen; i++) {
      str = fillStr + str;
    }
  }

  return str;
};

export const groupBy = (array, groupByFn, sortFn) => {
  const groups = {};
  array.forEach(function (item) {
    const group = JSON.stringify(groupByFn(item));
    groups[group] = groups[group] || [];
    groups[group].push(item);
  });

  let result = [];
  Object.keys(groups).map(function(group) {
    if (sortFn) {
      groups[group].sort(sortFn);
    }
    result.push(groups[group]);
  });

  return result;
};

export const deepClone = (obj, filterFn = undefined) => {
  const _toString = Object.prototype.toString;

  if (!obj || typeof obj !== 'object') {
    return obj;
  }

  if (obj.nodeType && 'cloneNode' in obj) {
    return obj.cloneNode(true);
  }

  if (_toString.call(obj) === '[object Date]') {
    return new Date(obj.getTime());
  }

  if (_toString.call(obj) === '[object RegExp]') {
    const flags = [];
    if (obj.global) { flags.push('g') }
    if (obj.multiline) { flags.push('m') }
    if (obj.ignoreCase) { flags.push('i') }

    return new RegExp(obj.source, flags.join(''));
  }

  const result = Array.isArray(obj) ? [] : obj.constructor ? new obj.constructor() : {};

  for (const key in obj) {
    if (typeof filterFn === 'undefined' || filterFn(key, obj)) {
      result[key] = deepClone(obj[key], filterFn);
    }
  }

  return result;
};

export const walkProp = (obj, walkFn) => {
  const _toString = Object.prototype.toString;

  if (!obj || typeof obj !== 'object') {
    return;
  }

  if (_toString.call(obj) === '[object Date]') {
    return;
  }

  if (_toString.call(obj) === '[object RegExp]') {
    return;
  }

  for (const key in obj) {
    walkFn(key, obj);
    walkProp(obj[key], walkFn);
  }
};

export const formatNumber = (number, pattern, prmNullValue) => {
  if(number == null || number === '') return prmNullValue || '';
  if(pattern == null || pattern === '') return number;

  if (!isNaN(pattern)) {
    const length = parseInt(pattern);
    if (length > 0) {
      pattern = '0.' + fillLength('', parseInt(pattern), '0') + '#';
    } else {
      pattern = '0.#';
    }
  }

  number = (number + '').replace(/,/g, '');
  if(isNaN(number)) return number;

  let patternLen = pattern.length;
  let pointIndex = pattern.indexOf('.');
  let groupIndex = pattern.indexOf(',');
  let pointLen = (pointIndex === -1 ? 0 : patternLen - pointIndex - 1);
  let result = parseFloat(number).toFixed(pointLen) + '';
  if(groupIndex !== -1) {
    if(pointIndex === -1) pointIndex = patternLen;
    let len = pointIndex - groupIndex - 1;
    let reg = new RegExp("(-?\\d+)(\\d{" + len + "})");
    while(reg.test(result)) {
      result = result.replace(reg, "$1,$2");
    }
  }

  if (pointLen > 0) {
    for (let i=pattern.length; i--; i > pattern.indexOf('.')) {
      if (pattern.charAt(i) === '0'
          || !(result.charAt(result.length - 1) === '0'
              || result.charAt(result.length - 1) === '.')) {
        break;
      }
      result = result.substring(0, result.length - 1);
      if (pattern.charAt(i) === '.') {
        break;
      }
    }
  }
  return result;
};

export const formatNumberWithCommas = (number) => {
  const parts = number.toString().split(".");
  const integerPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  if (parts.length > 1) {
    return integerPart + "." + parts[1];
  } else {
    return integerPart;
  }
};

export const formatMoney = (number, precision) => {
  const fixedAmount = Number(number).toFixed(precision || 0);
  const [integerPart, decimalPart] = fixedAmount.split('.');
  const integerWithCommas = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',');
  return decimalPart ? `${integerWithCommas}.${decimalPart}` : `${integerWithCommas}`;
};

export const isDeepEqual = (obj1, obj2) => {
  if (obj1 === obj2) {
    return true;
  }

  if (typeof obj1 !== typeof obj2) {
    return false;
  }

  if (Array.isArray(obj1) !== Array.isArray(obj2)) {
    return false;
  }

  if (typeof obj1 === 'object' && obj1 !== null && obj2 !== null) {
    const keys1 = Object.keys(obj1);
    const keys2 = Object.keys(obj2);

    if (keys1.length !== keys2.length) {
      return false;
    }

    for (const key of keys1) {
      if (!Object.prototype.hasOwnProperty.call(obj2, key)) {
        return false;
      }

      if (!isDeepEqual(obj1[key], obj2[key])) {
        return false;
      }
    }

    return true;
  }

  return obj1 === obj2;
};

const getFunArgs = (func) => {
  return func.toString()
    .match(/function\s.*?\(([^)]*)\)/)[1]
    .split(',')
    .map(arg => arg.replace(/\/\*.*\*\//, '').trim())
    .filter(args => args);
};

/**
 * 遍历所有树节点
 * @param tree
 * @param walkFn
 */
const walkTree = (tree, walkFn) => {
  for (let node of tree.children || []) {
    walkFn && walkFn(node);
    walkTree(node, walkFn);
  }
}

/**
 * 遍历所有树节点
 * @param tree
 * @param walkFn
 */
const asyncWalkTree = async(tree, walkFn) => {
  for (let node of tree.children || []) {
    walkFn && await walkFn(node);
    await asyncWalkTree(node, walkFn);
  }
}

const formatTime = (milliseconds) => {
  if (!milliseconds && milliseconds !== 0) return '';
  if (milliseconds < 1000) return milliseconds.toFixed(0) + ' 毫秒';
  let dayMilliseconds = 24 * 60 * 60 * 1000;
  let hourMilliseconds = 60 * 60 * 1000;
  let minuteMilliseconds = 60 * 1000;

  let days = parseInt(milliseconds / dayMilliseconds);
  let hours = parseInt(milliseconds % dayMilliseconds / hourMilliseconds);
  let minutes = parseInt(milliseconds % hourMilliseconds / minuteMilliseconds);
  let seconds = parseInt(milliseconds % minuteMilliseconds / 1000);

  let result = [];
  if (days) result.push(days + ' 天 ');
  if (hours || days) result.push(hours + ' 小时 ');
  if (minutes || hours || days) result.push(minutes + ' 分 ');
  if (seconds || minutes || hours || days) result.push(seconds + ' 秒');
  return result.join('');
};

const UNITS = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const formatBytes = (bytes, decimals = 2) => {
  if (!bytes) return `0 ${UNITS[0]}`;
  const k = 1024;
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${UNITS[i]}`;
};

const randomString = (length) => {
  const characters = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
  let result = '';
  for (let i = 0; i < length; i++) {
    const randomIndex = Math.floor(Math.random() * characters.length);
    result += characters[randomIndex];
  }
  return result;
};

const getContextPath = () => {
  const pathName = window.location.pathname;
  const firstSlash = pathName.indexOf('/', 1);
  return firstSlash !== -1 ? pathName.substring(0, firstSlash) : '';
};

const getBaseUrl = () => {
  const loc = window.location;
  const contextPath = getContextPath();
  return `${loc.protocol}//${loc.host}${contextPath}`;
};

const escapeHtml = (str) => {
  if (!str) return '';
  return str.replaceAll(/</ig, '&lt;').replaceAll(/>/ig, '&gt;')
};

const findNode = (id, children) => {
  if (children === undefined || children == null || id === undefined || id == null) {
    return null;
  }

  for (let i = 0; i < children.length; i++) {
    if (children[i].id !== '0' && children[i].id === id) {
      return children[i];
    }

    if (children[i].children !== undefined && children[i].children != null) {
      const node = findNode(id, children[i].children);
      if (node) {
        return node;
      }
    }
  }

  return null;
};

const getParentNodes = (tree, targetId) => {
  const result = [];

  function traverse(node, path) {
    if (!node) return false;

    if (node.id === targetId) {
      return true;
    }

    for (const child of (node.children || [])) {
      path.push(node);
      if (traverse(child, path)) {
        return true;
      }
      path.pop();
    }

    return false;
  }

  traverse(tree, result);
  return result;
};

const findNodeById = (tree, targetId) => {
  function traverse(node) {
    if (!node) return null;

    if (node.id === targetId) {
      return node;
    }

    for (const child of node.children) {
      const foundNode = traverse(child);
      if (foundNode) {
        return foundNode;
      }
    }

    return null;
  }

  return traverse(tree);
};

const getMoneyInt = (money) => {
  money = parseFloat(money || '0').toFixed(2);
  return money.split('.')[0];
};

const getMoneyScale = (money) => {
  money = parseFloat(money || '0').toFixed(2);
  return money.split('.')[1];
};

const debounce = (fn, delay = 100) => {
  let timer = null;
  return function (...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
};

const throttle = (func, limit) => {
  let lastFunc;
  let lastRan;
  return function() {
    const context = this;
    const args = arguments;
    if (!lastRan) {
      func.apply(context, args);
      lastRan = Date.now();
    } else {
      clearTimeout(lastFunc);
      lastFunc = setTimeout(function() {
        if ((Date.now() - lastRan) >= limit) {
          func.apply(context, args);
          lastRan = Date.now();
        }
      }, limit - (Date.now() - lastRan));
    }
  }
};

const runTask = async(list, concurrency, handler) => {
  const results = [];
  const executing = new Set();

  for (const item of list) {
    const p = Promise.resolve().then(() => handler(item));
    results.push(p);
    executing.add(p);

    const clean = () => executing.delete(p);
    p.then(clean).catch(clean);

    if (executing.size >= concurrency) {
      await Promise.race(executing);
    }
  }

  return Promise.all(results);
};

export default {
  uuid,
  uuid36,
  uuids,
  groupBy,
  deepClone,
  walkProp,
  walkTree,
  asyncWalkTree,
  formatMoney,
  formatNumber,
  formatNumberWithCommas,
  isDeepEqual,
  getFunArgs,
  formatTime,
  formatBytes,
  randomString,
  getContextPath,
  getBaseUrl,
  escapeHtml,
  findNode,
  getParentNodes,
  findNodeById,
  getMoneyInt,
  getMoneyScale,
  debounce,
  throttle,
  runTask
};